/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.architexa.org.eclipse.gef.tools;

import com.architexa.org.eclipse.draw2d.PositionConstants;
import com.architexa.org.eclipse.draw2d.geometry.Point;
import com.architexa.org.eclipse.draw2d.geometry.PrecisionRectangle;
import com.architexa.org.eclipse.draw2d.geometry.Rectangle;
import com.architexa.org.eclipse.gef.EditPart;
import com.architexa.org.eclipse.gef.EditPartViewer;
import com.architexa.org.eclipse.gef.Request;
import com.architexa.org.eclipse.gef.SharedCursors;
import com.architexa.org.eclipse.gef.SnapToHelper;
import com.architexa.org.eclipse.gef.requests.CreateRequest;
import com.architexa.org.eclipse.gef.requests.CreationFactory;

import org.eclipse.swt.graphics.Cursor;



/**
 * The CreationTool creates new {@link EditPart EditParts} via a {@link CreationFactory}.
 * If the user simply clicks on the viewer, the default sized EditPart will be created at
 * that point.  If the user clicks and drags, the created EditPart will be sized based on
 * where the user clicked and dragged.
 */
public class CreationTool
	extends TargetingTool
{

/**
 * Property to be used in {@link AbstractTool#setProperties(java.util.Map)} for 
 * {@link #setFactory(CreationFactory)}.
 */
public static final Object PROPERTY_CREATION_FACTORY = "factory"; //$NON-NLS-1$

private CreationFactory factory;
private SnapToHelper helper;


/**
 * Default constructor.  Sets the default and disabled cursors.
 */
public CreationTool() {
	setDefaultCursor(SharedCursors.CURSOR_TREE_ADD);
	setDisabledCursor(SharedCursors.NO);
}

/**
 * Constructs a new CreationTool with the given factory.
 * @param aFactory the creation factory
 */
public CreationTool(CreationFactory aFactory) {
	this();
	setFactory(aFactory);
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#applyProperty(java.lang.Object, java.lang.Object)
 */
protected void applyProperty(Object key, Object value) {
	if (PROPERTY_CREATION_FACTORY.equals(key)) {
		if (value instanceof CreationFactory)
			setFactory((CreationFactory)value);
		return;
	}
	super.applyProperty(key, value);
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#calculateCursor()
 */
protected Cursor calculateCursor() {
	/*
	 * Fix for Bug# 66010
	 * The following two lines of code were added for the case where a tool is
	 * activated via the keyboard (that code hasn't been released yet).  However, they
	 * were causing a problem as described in 66010.  Since the keyboard activation code
	 * is not being released for 3.0, the following lines are being commented out.
	 */
//	if (isInState(STATE_INITIAL)) 
//		return getDefaultCursor();
	return super.calculateCursor();
}

/**
 * Creates a {@link CreateRequest} and sets this tool's factory on the request.
 * @see com.architexa.org.eclipse.gef.tools.TargetingTool#createTargetRequest()
 */
protected Request createTargetRequest() {
	CreateRequest request = new CreateRequest();
	request.setFactory(getFactory());
	return request;
}

/**
 * @see com.architexa.org.eclipse.gef.Tool#deactivate()
 */
public void deactivate() {
	super.deactivate();
	helper = null;
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#getCommandName()
 */
protected String getCommandName() {
	return REQ_CREATE;
}

/**
 * Cast the target request to a CreateRequest and returns it.
 * @return the target request as a CreateRequest
 * @see TargetingTool#getTargetRequest()
 */
protected CreateRequest getCreateRequest() {
	return (CreateRequest)getTargetRequest();
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#getDebugName()
 */
protected String getDebugName() {
	return "Creation Tool";//$NON-NLS-1$
}

/**
 * Returns the creation factory used to create the new EditParts.
 * @return the creation factory
 */
protected CreationFactory getFactory() {
	return factory;
}

/**
 * The creation tool only works by clicking mouse button 1 (the left mouse button in a
 * right-handed world).  If any other button is pressed, the tool goes into an invalid
 * state.  Otherwise, it goes into the drag state, updates the request's location and 
 * calls {@link TargetingTool#lockTargetEditPart(EditPart)} with the edit part that was
 * just clicked on.
 * 
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleButtonDown(int)
 */
protected boolean handleButtonDown(int button) {
	if (button != 1) {
		setState(STATE_INVALID);
		handleInvalidInput();
		return true;
	}
	if (stateTransition(STATE_INITIAL, STATE_DRAG)) {
		getCreateRequest().setLocation(getLocation());
		lockTargetEditPart(getTargetEditPart());
		// Snap only when size on drop is employed
		if (getTargetEditPart() != null)
			helper = (SnapToHelper)getTargetEditPart().getAdapter(SnapToHelper.class);
	}
	return true;
}

/**
 * If the tool is currently in a drag or drag-in-progress state, it goes into the 
 * terminal state, performs some cleanup (erasing feedback, unlocking target edit part), 
 * and then calls {@link #performCreation(int)}.
 * 
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleButtonUp(int)
 */
protected boolean handleButtonUp(int button) {
	if (stateTransition(STATE_DRAG | STATE_DRAG_IN_PROGRESS, STATE_TERMINAL)) {
		eraseTargetFeedback();
		unlockTargetEditPart();
		performCreation(button);
	}

	setState(STATE_TERMINAL);
	handleFinished();

	return true;
}

/**
 * Updates the request, sets the current command, and asks to show feedback.
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleDragInProgress()
 */
protected boolean handleDragInProgress() {
	if (isInState(STATE_DRAG_IN_PROGRESS)) {
		updateTargetRequest();
		setCurrentCommand(getCommand());
		showTargetFeedback();
	}
	return true;
}

/**
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleDragStarted()
 */
protected boolean handleDragStarted() {
	return stateTransition(STATE_DRAG, STATE_DRAG_IN_PROGRESS);
}

/**
 * If the user is in the middle of creating a new edit part, the tool erases feedback and 
 * goes into the invalid state when focus is lost.
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleFocusLost()
 */
protected boolean handleFocusLost() {
	if (isInState(STATE_DRAG | STATE_DRAG_IN_PROGRESS)) {
		eraseTargetFeedback();
		setState(STATE_INVALID);
		handleFinished();
		return true;
	}
	return false;
}

/**
 * @see com.architexa.org.eclipse.gef.tools.TargetingTool#handleHover()
 */
protected boolean handleHover() {
	if (isInState(STATE_INITIAL))
		updateAutoexposeHelper();
	return true;
}

/**
 * Updates the request and mouse target, gets the current command and asks to show
 * feedback.
 * @see com.architexa.org.eclipse.gef.tools.AbstractTool#handleMove()
 */
protected boolean handleMove() {
	updateTargetRequest();
	updateTargetUnderMouse();
	setCurrentCommand(getCommand());
	showTargetFeedback();
	return true;
}

/**
 * Executes the current command and selects the newly created object.  The button that was
 * released to cause this creation is passed in, but since {@link #handleButtonDown(int)}
 * goes into the invalid state if the button pressed is not button 1, this will always be 
 * button 1.  
 * 
 * @param button the button that was pressed
 */
protected void performCreation(int button) {
	EditPartViewer viewer = getCurrentViewer();
	executeCurrentCommand();
	selectAddedObject(viewer);
}

/*
 * Add the newly created object to the viewer's selected objects.
 */
private void selectAddedObject(EditPartViewer viewer) {
	final Object model = getCreateRequest().getNewObject();
	if (model == null || viewer == null)
		return;
	Object editpart = viewer.getEditPartRegistry().get(model);
	if (editpart instanceof EditPart) {
		//Force the new object to get positioned in the viewer. 
		viewer.flush();
		viewer.select((EditPart)editpart);
	}
}

/**
 * Sets the creation factory used to create the new edit parts.
 * @param factory the factory
 */
public void setFactory(CreationFactory factory) {
	this.factory = factory;
}

/**
 * Sets the location (and size if the user is performing size-on-drop) of the request.
 * @see com.architexa.org.eclipse.gef.tools.TargetingTool#updateTargetRequest()
 */
protected void updateTargetRequest() {
	CreateRequest req = getCreateRequest();
	if (isInState(STATE_DRAG_IN_PROGRESS)) {
		Point loq = getStartLocation();
		Rectangle bounds = new Rectangle(loq, loq);
		bounds.union(loq.getTranslated(getDragMoveDelta()));
		req.setSize(bounds.getSize());
		req.setLocation(bounds.getLocation());
		req.getExtendedData().clear();
		if (!getCurrentInput().isAltKeyDown() && helper != null) {
			PrecisionRectangle baseRect = new PrecisionRectangle(bounds);
			PrecisionRectangle result = baseRect.getPreciseCopy();
			helper.snapRectangle(req, PositionConstants.NSEW, 
				baseRect, result);
			req.setLocation(result.getLocation());
			req.setSize(result.getSize());
		}
	} else {
		req.setSize(null);
		req.setLocation(getLocation());
	}
}

}
